package io.summer.gadget.channel.core.anno.processor;

import com.google.auto.service.AutoService;
import io.summer.gadget.channel.core.TopicListener;
import io.summer.gadget.channel.core.anno.ChannelTopic;
import org.springframework.util.ClassUtils;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

//must be processor
@AutoService(Processor.class)
public class ChannelClientProcessor extends AbstractProcessor {
  private Filer filer;
  private Elements elementUtils;
  private Messager messager;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    filer = processingEnv.getFiler();
    elementUtils = processingEnv.getElementUtils();
    messager = processingEnv.getMessager();
  }

  private String createSourceCode(Map<String, String> properties) {
    String template = "\n" +
      "package $package;\n" +
      "import $topic_listener;\n" +
      "public class $class_name implements TopicListener<$input_type, $output_type> {\n" +
      "  private $topic_service topicService;\n" +
      "  public $class_name($topic_service topicService) {\n" +
      "    this.topicService = topicService;\n" +
      "  }\n" +
      "  @Override\n" +
      "  public $output_type apply($input_type input) {\n" +
      "    return topicService.$method(input);\n" +
      "  }\n" +
      "}\n";
    for (Map.Entry<String, String> p : properties.entrySet()) {
      template = template.replaceAll("\\$" + p.getKey(), p.getValue());
    }
    return template;
  }

  private static String getMessageHandlerName(Element serviceType, Element handler) {
    return serviceType.getSimpleName() + handler.getSimpleName().toString() + "Handler";
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for (TypeElement typeElement : annotations) {
      Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
      for (Element element : elements) {
        ExecutableType method = (ExecutableType) element.asType();
        if (method.getParameterTypes().size() != 1) {
          messager.printMessage(Diagnostic.Kind.ERROR, "handler must has one Parameter", element);
        }
        Element serviceType = element.getEnclosingElement();
        Map<String, String> properties = new HashMap<>(5);
        properties.put("package", elementUtils.getPackageOf(serviceType).getQualifiedName().toString());
        String className = getMessageHandlerName(serviceType, element);
        properties.put("class_name", className);
        properties.put("input_type", method.getParameterTypes().get(0).toString());
        properties.put("output_type", method.getReturnType().toString());
        properties.put("topic_service", ((TypeElement) serviceType).getQualifiedName().toString());
        properties.put("method", element.getSimpleName().toString());
        properties.put("topic_listener", ClassUtils.getQualifiedName(TopicListener.class));
        String source = createSourceCode(properties);
        try {
          JavaFileObject fileObject = filer.createSourceFile(className);
          Writer writer = fileObject.openWriter();
          writer.write(source);
          writer.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return true;
  }

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> s = new HashSet<>();
    s.add(ChannelTopic.class.getName());
    return s;
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }
}
